﻿using System;
using System.Linq;
using System.ComponentModel;
using System.Xml.Serialization;
using System.Collections.Specialized;

namespace LaunchPad.Core
{
    public class EffectTrack : Track, IIdentifiable, IMappable
    {
        public EffectTrack() : this(new DefaultProduct())
        {
        }

        public EffectTrack(Product product)
        {
            Id = Guid.NewGuid();
            Product = product;
            Timeline.CollectionChanged += TimelineCollectionChanged;
            Location = new ObservablePoint();
            Enabled = true;
        }

        private Guid _id;
        private Node _node;
        private string _group;
        private Product _product;
        private EffectPort _port;
        private ObservablePoint _location;
        private bool _hot;
        private bool _enabled;
        private bool _hovered;
        private string _nodeId;
        
        [XmlAttribute]
        public Guid Id
        {
            get { return _id; }
            set
            {
                if (_id != value)
                {
                    _id = value;
                    OnPropertyChanged("Id");
                }
            }
        }

        public string Name
        { 
            get { return Product.Name; } 
        }

        [XmlAttribute]
        public string Group
        {
            get { return _group; }
            set
            {
                if (_group != value)
                {
                    _group = value;
                    OnPropertyChanged("Group");
                }
            }
        }

        [XmlIgnore]
        public string Description
        {
            get
            {
                if (Product == null)
                    return string.Format("Unknown, {0}", Address);
                return string.Format("{0} Effect, {1}", Product.Name, Address);
            }
        }

        [XmlIgnore]
        public bool IsAssigned
        {
            get { return Node != null; }
        }

        [XmlIgnore]
        public Product Product
        {
            get { return _product; }
            set
            {
                if (_product != value)
                {
                    _product = value;

                    if(_product != null)
                        _product.PropertyChanged += ProductPropertyChanged;

                    OnPropertyChanged("Product");

                    UpdateTimeline();
                }
            }
        }

        [XmlElement("ProductRef")]
        public IdRef<Product> ProductReference
        {
            get
            {
                if (Product == null)
                    return null;
                return new IdRef<Product>(Product);
            }

            set
            {
                if (value != null)
                {
                    Product product;

                    if (ProductFactory.Instance.TryGetProduct(value, out product))
                    {
                        Product = product;
                        OnPropertyChanged("ProductReference");
                    }
                }
            }
        }

        [XmlAttribute]
        public string NodeId
        {
            get
            {
                if (Node == null)
                    return _nodeId;
                return Node.SerialNumber.Value.ToString();
            }

            set
            {
                if (_nodeId != value)
                {
                    _nodeId = value;
                    if (Field != null)
                    {
                        var node = Field.Nodes.FirstOrDefault(n => n.SerialNumber.Value.ToString() == value);
                        if (node != null && node != Node)
                        {
                            Node = node;
                        }
                    }

                    OnPropertyChanged("NodeId");
                }
            }
        }

        [XmlIgnore]
        public Node Node
        {
            get
            {
                return _node;
            }

            set
            {
                if (_node != value)
                {
                    _node = value;
                    _nodeId = _node.SerialNumber.Value.ToString();
                    OnPropertyChanged("Node");
                    OnPropertyChanged("Name");
                    OnPropertyChanged("Address");
                    OnPropertyChanged("Description");
                    OnPropertyChanged("IsAssigned");
                }
            }
        }

        public ObservablePoint Location
        {
            get { return _location; }
            set
            {
                if (_location != value)
                {
                    _location = value;
                    OnPropertyChanged("Location");
                }
            }
        }

        [XmlAttribute]
        public EffectPort Port
        {
            get
            {
                return _port;
            }

            set
            {
                if (_port != value)
                {
                    // turn off old port
                    if (Node != null)
                        Node.SetEffectState(_port, false);

                    _port = value;

                    // refresh state
                    if (Node != null)
                        Node.SetEffectState(_port, IsHot);

                    OnPropertyChanged("Port");
                    OnPropertyChanged("Address");
                }
            }
        }

        [XmlIgnore]
        public bool IsHot
        {
            get
            {
                return _hot;
            }

            set
            {
                if (_hot != value)
                {
                    if(Node != null)
                        Node.SetEffectState(Port, value);

                    _hot = value;

                    OnPropertyChanged("IsHot");
                }
            }
        }

        [XmlAttribute]
        public bool Enabled
        {
            get
            {
                return _enabled;
            }

            set
            {
                if (_enabled != value)
                {
                    _enabled = value;
                    OnPropertyChanged("Enabled");
                }
            }
        }

        [XmlIgnore]
        public bool IsHovered
        {
            get { return _hovered; }
            set
            {
                if (_hovered != value)
                {
                    _hovered = value;
                    OnPropertyChanged("IsHovered");
                }
            }
        }

        EffectTrackEvent CreateEvent(TimeSpan time)
        {
            var e = new EffectTrackEvent
                        {
                            Time = time,
                            LeadIn = Product.LeadIn, 
                            LeadOut = Product.LeadOut
                        };
            return e;
        }

        public void AddEvent(TimeSpan time)
        {
            EffectTrackEvent e = CreateEvent(time);
            Timeline.Add(e);
        }

        public bool TryAddEvent(TimeSpan time)
        {
            EffectTrackEvent e = CreateEvent(time);
            return TryAddEvent(e);
        }

        [XmlIgnore]
        public string Address
        {
            get
            {
                if (!IsAssigned)
                    return "Unassigned";

                return string.Format("{0}@{1}", Port, Node.Name);
            }
        }

        public override string ToString()
        {
            return Description;
        }

        void ProductPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            switch (e.PropertyName)
            {
                case "LeadIn":
                case "LeadOut":
                    UpdateTimeline();
                    break;
            }
        }

        void UpdateTimeline()
        {
            foreach (var ete in Timeline)
            {
                ete.LeadIn = Product.LeadIn;
                ete.LeadOut = Product.LeadOut;
            }
        }

        void TimelineCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            if (e.NewItems != null)
            {
                foreach (EffectTrackEvent ete in e.NewItems)
                    ete.Effect = this;
            }

            if (e.OldItems != null)
            {
                foreach (EffectTrackEvent ete in e.OldItems)
                    ete.Effect = null;
            }
        }
    }
}
